/**
 * 游戏扩展脚本，用于处理前端资源文件的版本控制和内容替换
 *
 * 主要功能包括：
 * 1. 版本控制：为JavaScript文件维护版本号，当文件内容发生变化时自动递增版本号
 * 2. 文件压缩支持：根据配置自动将JS文件替换为压缩版本(.min.js)
 * 3. 文件重命名支持：支持根据配置对文件进行重命名处理
 * 4. HTML内容替换：在构建过程中替换HTML文件中的指定内容
 * 5. 路径处理：将相对路径转换为绝对路径，确保资源正确加载
 * 6. JSON配置更新：更新JS文件中嵌入的JSON配置对象的版本信息
 * 7. 环境适配：根据不同环境（开发/测试/生产）执行不同的处理逻辑
 *
 * 工作流程：
 * - 监听文件变化，当index.html同目录下的文件发生变化时触发处理
 * - 读取配置文件，根据配置进行文件替换和压缩
 * - 更新JavaScript文件的版本号并写入缓存文件
 * - 处理HTML中的资源引用路径
 * - 更新index.js中的JSON配置对象版本信息
 */
package com.script

import com.Crc32
import com.CustomScript
import com.entity.PackPath
import com.utils.PropertiesUtils
import com.utils.ScriptUtils.appConfig
import com.utils.ScriptUtils.contrastFile
import com.utils.ScriptUtils.script
import com.utils.YamlUtil
import extend.printLog
import org.apache.logging.log4j.kotlin.logger
import org.json.JSONObject
import org.jsoup.Jsoup
import org.jsoup.nodes.DataNode
import org.jsoup.nodes.Document

// 版本控制文件名字
private val versionName = "version.properties"

appConfig.ignoreList.add(versionName)

private val propertiesUtils = PropertiesUtils()

script.onChangeIndex = ::changeIndex
script.customScript.add(object : CustomScript {
    override fun onBeforeClose() {
        propertiesUtils.store()
    }
})

// 处理入口文件配置
private fun changeIndex() {
    logger.info("gameExtend-> 执行入口文件修改")

    val addFile = contrastFile.addFileList
    val updateFile = contrastFile.updateFileList

    val fileList = addFile + updateFile

    // 只要是和index.html 在同一个目录的文件变动  就更新一次index.html
    var needUpdateIndex = false

    var indexHtmlPath = fileList.find {
        appConfig.outDir.relativize(it).pathString == "index.html"
    }
    if (indexHtmlPath == null) {
        for (tempFile in fileList) {
            // 修改成 输出目录只要存在index.html就要执行修改
            if (tempFile.parent?.absolutePathString == appConfig.outDir.absolutePathString) {
                indexHtmlPath = appConfig.outDir.resolve("index.html")
                val scanFile = contrastFile.scanAllPath.find {
                    contrastFile.getRelativePath(it, false) == "index.html"
                }
                if (scanFile != null) {
                    printLog("将找到的index.html 复制到out目录 $scanFile->$indexHtmlPath")
                    scanFile.copyTo(indexHtmlPath)
                    contrastFile.updateFileList.add(indexHtmlPath)
                    needUpdateIndex = true
                }
                break
            }
        }
    } else needUpdateIndex = true
    replaceHtmlHandler(needUpdateIndex, indexHtmlPath)
}


// 替换html内容
private fun replaceHtmlHandler(needUpdateIndex: Boolean, indexHtmlPath: PackPath?) {
    if (indexHtmlPath == null) error("修改index.html 未发现文件html")
    val addFile = contrastFile.addFileList
    val updateFile = contrastFile.updateFileList

    val fileList = (addFile + updateFile) as MutableSet<PackPath>

    val indexUrl = script.property.getOrDefault("indexUrl", "") as String

    // 强行添加 index.js文件修改变动 以便版本号可以变动

    var addIndexJsFile = false
    var indexJsPath = fileList.find {
        it.name.endsWith("index.js", true)
    }
    if (indexJsPath == null) {
        val scanIndexJsPath = contrastFile.scanAllPath.find {
            it.name.endsWith("index.js", true)
        }
        if (scanIndexJsPath != null) {
            addIndexJsFile = true
            indexJsPath = appConfig.outDir.resolve(scanIndexJsPath.name)
            scanIndexJsPath.copyTo(indexJsPath)
            printLog("html更新中 未发现index更新, 添加index.js同步更新 $scanIndexJsPath->$indexJsPath")
            fileList.add(indexJsPath)
        }
    }

    var context = indexHtmlPath.readText()
    // 获取替换文件对于的配置文件名字
    val fileNameScript = "${indexHtmlPath.nameWithoutExtension}Replace"
    logger.info("gameExtend-> 替换文件配置名字=$fileNameScript")
    // 获取当前替换文件的配置文件信息
    val replaceMap = appConfig.replace[fileNameScript]!!
    var replaceScript = replaceMap.values.firstOrNull()
    // 获取指定环境的配置文件 没有就勇士第一个
    if (replaceMap.containsKey(YamlUtil.unpackEnvName)) {
        replaceScript = replaceMap[YamlUtil.unpackEnvName]!!
    }

    if (replaceScript != null) {
        for ((key, value) in replaceScript) {
            logger.info("gameExtend-> 执行替换内容. $key -> $value")
            context = context.replace(key, value)
        }
    }

    var doc = Jsoup.parse(context)
    val scripts = doc.select("script")
    val link = doc.select("link")

    //  如果是上传正式  取消注释
    if (YamlUtil.unpackEnvId == 3) {
        val el = scripts.first()!!
        var html = el.data()
        html = html.replace("/*", "")
        html = html.replace("*/", "")
        val dataNode = DataNode(html)
        el.empty()
        el.appendChild(dataNode)
    }

    // 有变更的js升级版本号
    propertiesUtils.read(appConfig.global.cachePath.resolve(versionName).toPath)
    // 获取所有的script标签
    val scriptSrc = doc.select("script[src]")

    for (versionJs in scriptSrc) {
        // 获取脚本url地址
        val oldScriptName = versionJs.attr("src")
        if (!oldScriptName.startsWith("https")) {
            val obj = checkVersion(PackPath(oldScriptName), fileList, indexHtmlPath)
            // 获取没有压缩后的文件名字
            val scriptName = obj.first
            val tempScriptName = obj.second
            val value = obj.third
            if (appConfig.runEnvironment().compreeJs)
                versionJs.attr("src", "$tempScriptName?v=$value")
            else
                versionJs.attr("src", "$scriptName?v=$value")
        }
    }

    context = doc.html()

    doc = Jsoup.parse(context)
    // 最后一步  修改绝对路径
    context = replaceAbsolute(doc, indexUrl)

    indexHtmlPath.writeText(context)

    // 同步修改 index.js
    if (needUpdateIndex && indexJsPath?.exists() == true) {
        context = indexJsPath.readText()
        // 获取需要处理的js文件数组

        val con = "\\{key..+,(\\s+)?v.(\\s+)?\\d+}".toRegex()
            .findAll(context)
            .map {
                it.value
            }
        for (jsonContent in con) {
            println(" key = $jsonContent")
            var jsonJavaScriptName: String
            if (jsonContent.contains("+")) { // 如果存在+号 说明是拼接json 需要特殊处理
                var keys = jsonContent.substringAfter("\"")
                keys = keys.substringBeforeLast("\"")
                var tempReplace = keys // 备份以备后面替换用
                keys = keys.substringBefore("\"")
                keys = keys.replace("_", ".js")
                jsonJavaScriptName = keys.replace("_", ".js")
                //println(tempReplace)
                //println(jsonJavaScriptName)
                //组成新的json
                val newJsonContent = jsonContent.replace(tempReplace, jsonJavaScriptName)
                //println("new = $newJsonContent")
                val json = updateJsonVersion(indexUrl, newJsonContent, fileList, indexHtmlPath)
                //println(json.toString())
                if (appConfig.runEnvironment().compreeJs) {
                    // 判断是否已经压缩过
                    //如果没有 那么改成压缩后的名字
                    if (!jsonJavaScriptName.contains(".min") && !jsonJavaScriptName.contains("min."))
                        jsonJavaScriptName = jsonJavaScriptName.replace(".js", ".min.js")

                    //如果没有 那么改成压缩后的名字
                    if (!tempReplace.contains(".min") && !tempReplace.contains("min."))
                        tempReplace = tempReplace.replace(".js", ".min.js")
                }
                val jsonStr = json.toString().replace(jsonJavaScriptName, tempReplace)
                //println(jsonStr)
                context = context.replace(jsonContent, jsonStr)
            } else {
                val json = updateJsonVersion(indexUrl, jsonContent, fileList, indexHtmlPath)
                //println(json.toString())
                context = context.replace(jsonContent, json.toString())
                //println(context)
            }
        }

        indexJsPath.writeText(context)

        script.methodMap["_replaceContent"]?.call(
            indexJsPath,
            "${indexJsPath.nameWithoutExtension}Replace", null
        )

        if (addIndexJsFile) {
            contrastFile.updateFileList.add(indexJsPath)
        }

    }
}

// 更新一个json版本控制
private fun updateJsonVersion(
    _indexUrl: String,
    jsonContent: String,
    fileList: MutableSet<PackPath>,
    indexFile: PackPath
): JSONObject {
    var indexUrl = _indexUrl
    if (indexUrl.isNotEmpty())
        indexUrl = "$indexUrl/"
    val json = JSONObject(jsonContent)
    val key = json.optString("key") // 获取key

    val obj = checkVersion(PackPath(key), fileList, indexFile)
    // 获取没有压缩后的文件名字
    val scriptName = obj.first
    val tempScriptName = obj.second
    val value = obj.third
    if (appConfig.runEnvironment().compreeJs)
        json.putOpt("key", indexUrl + tempScriptName)
    else
        json.putOpt("key", indexUrl + scriptName)
    json.putOpt("v", value)
    return json
}

private fun replaceAbsolute(doc: Document, baseUrl: String): String {
    if (baseUrl.isEmpty())
        return doc.html()

    val imgs = doc.select("img[src]")
    for (element in imgs) {
        val imgssrc = element.attr("src")
        if (!imgssrc.startsWith("http://") && !imgssrc.startsWith("https://"))
            element.attr("src", "$baseUrl/$imgssrc")
    }

    val scripts = doc.select("script[src]")
    for (element in scripts) {
        val scriptssrc = element.attr("src")
        if (!scriptssrc.startsWith("http://") && !scriptssrc.startsWith("https://"))
            element.attr("src", "$baseUrl/$scriptssrc")
    }
    return doc.html()
}

// 检查版本变动
private fun checkVersion(
    scriptPath: PackPath,
    fileList: MutableSet<PackPath>,
    indexFile: PackPath
): Triple<String, String, String> {
    val oldScriptName = scriptPath.name
    // 获取没有压缩后的文件名字
    var scriptName = oldScriptName.replace(".min.js", ".js")

    logger.info("gameExtend-> 处理文件 $oldScriptName")

    // 此文件是否修改过名字
    var isChangeFile = false
    // 如果有修改名字  检查此脚本名字是否为修改的名字 如果是修改
    if (appConfig.replaceNameFile.isNotEmpty()) {
        for ((key, value) in appConfig.replaceNameFile) {
            if (key == oldScriptName) {
                scriptName = value
                logger.info("gameExtend-> 激活改名 $oldScriptName")
                isChangeFile = true
                break
            }
        }
    }

    // 此文件是否需要压缩
    var isFileCompress = false
    for (jsStr in appConfig.compress.js.config) {
        if (contrastFile.validatePath(oldScriptName, jsStr, ".min.js")) {
            logger.info("gameExtend-> 激活压缩 $oldScriptName")
            isFileCompress = true
            break
        }
    }

    var tempScriptName = scriptName
    if (isFileCompress) {
        // 如果脚本没有压缩过 处理成压缩后的名字
        if (!scriptName.contains(".min."))
            tempScriptName = scriptName.replace(".js", ".min.js")
    }

    // 不曾改名 也不需要压缩  那么跳过这个脚本的名字变动
    if (!isFileCompress && !isChangeFile) {
        logger.info("gameExtend-> 此文件不需要压缩和改名 $scriptName")
        tempScriptName = oldScriptName
        scriptName = oldScriptName
    }

    // 获取缓存的版本号  没有就新建一个
    var value = propertiesUtils.get(tempScriptName) ?: "0"

    // 查询变化列表里面是否存在此文件
    val addIndex = fileList.find {
        val tempScriptName = contrastFile.changeDiskCharacter(tempScriptName)!!
        val scriptName = contrastFile.changeDiskCharacter(scriptName)!!
        val pathStr = it.pathString
        (pathStr.endsWith(tempScriptName) || pathStr.endsWith(scriptName))
    }

    val file = indexFile.parent!!.resolve(scriptName)
    // 如果存在 更新hash
    if (addIndex != null && file.exists())
        value = Crc32.crc32Hex(file)

    println(
        "*******-------  check" +
                " $oldScriptName $scriptName" +
                " $value $tempScriptName ${file.absolutePathString}"
    )
    // 存储到缓存库
    propertiesUtils.add(tempScriptName, value)

    // 返回 没有压缩的名字 压缩后的名字 版本号
    return Triple(scriptName, tempScriptName, value)

}